import pandas as pd
import numpy as np
import  matplotlib.pyplot as plt
from statsmodels.graphics.tsaplots import plot_acf
from statsmodels.graphics.tsaplots import plot_pacf
from statsmodels.stats.diagnostic import acorr_ljungbox
from statsmodels.tsa.stattools import adfuller as ADF
from statsmodels.tsa.arima.model import ARIMA

pd.set_option('display.float_format', '{:.20f}'.format) #改变显示精度
pd.set_option('display.precision', 20)

def tagADF(data): #ADF平稳性检测，主要看p值
    t = ADF(data,autolag='AIC')
    result = pd.DataFrame(index=[
            "Test Statistic Value", "p-value", "Lags Used", 
            "Number of Observations Used", 
            "Critical Value(1%)", "Critical Value(5%)", "Critical Value(10%)"
        ], columns=['value']
    )
    result.loc['Test Statistic Value', 'value'] = t[0]
    result.loc['p-value', 'value'] = t[1]
    result.loc['Lags Used', 'value'] = t[2]
    result.loc['Number of Observations Used', 'value'] = t[3]
    for key, value in t[4].items():
      result.loc['Critical Value(%s)' % key, 'value'] = value

    return result['value']
  
  
# 1、生成一个不平稳的时间序列（随机游走）
np.random.seed(0)
n = 200
x = np.cumsum(np.random.randn(n))  # 累加生成非平稳序列
print(x.shape)

data = pd.DataFrame(x, columns=['value']) # 转换为Pandas的DataFrame格式，便于处理

plt.rcParams['font.sans-serif']=['SimHei'] # 设置中文显示，用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
fig = plt.figure()
ax1 = plt.subplot(231)
ax2 = plt.subplot(232)
ax3 = plt.subplot(233)
ax4 = plt.subplot(234)
ax5 = plt.subplot(235)
ax6 = plt.subplot(236)

data.plot(ax = ax1) ## pandas的plot函数绘图，查看趋势图初步判断是否平稳，结果是：有增长趋势，不平稳

# 2、自相关图ACF和PACF分析（）
plot_acf(data, ax = ax2) #自相关图既不是拖尾也不是截尾。以上的图的自相关是一个三角对称的形式，这种趋势是单调趋势的典型图形，说明这个序列不是平稳序列
plot_pacf(data,ax=ax3)
print('原始序列的ADF检验结果为:',tagADF(data))  # 添加标签后展现，u表示取一列
# 平稳判断：得到统计量大于三个置信度(1%,5%,10%)临界统计值，p值显著大于0.05，该序列为非平稳序列。
# 备注：得到的统计量显著小于3个置信度（1%，5%，10%）的临界统计值时，为平稳 此时p值接近于0 此处不为0，尝试增加数据量，原数据太少

# 3、进行数据差分，一般一阶差分就可以
D_data = data.diff(1).dropna()
#差分图趋势查看
D_data.plot(ax=ax4) 
plot_acf(D_data,ax = ax5)#自相关图
plot_pacf(D_data,ax=ax6)#偏自相关图
plt.show()
print(u'差分序列的ADF检验结果为：', tagADF(D_data)) #平稳性检测 结果：Test Statistic Value值小于两个水平值，p值显著小于0.05，一阶差分后序列为平稳序列。

# 4、白噪声检验
print(u'差分序列的白噪声检验结果为：', acorr_ljungbox(D_data, lags=1))  # 分别为stat值（统计量）和P值
# P值小于0.05，所以一阶差分后的序列为平稳非白噪声序列。白噪声的自相关系数为全0.这里的lags和差分阶数应该设置的差不多

      
# 5、 利用BIC（贝叶斯信息）准则 p，q定阶，选择bic值最小的一组组合

if not isinstance(D_data.index, pd.DatetimeIndex):#需要日期索引来作为ARIMA模型的输入
    start_date = pd.Timestamp('2023-01-01')  # 根据你的数据调整起始日期，创建日期索引
    dates = pd.date_range(start=start_date, periods=len(D_data), freq='D')
    D_data = pd.Series(D_data.values.reshape(-1), index=dates)
    print("已创建日期索引")

pmax = int(len(D_data)/10) #一般阶数不超过length/10

qmax = int(len(D_data)/10) #一般阶数不超过length/10


bic_matrix = [] #创建bic矩阵
# for p in range(pmax+1): #遍历检索，根据最小化bic值原则找到最佳pq值
#   tmp = []
#   for q in range(qmax+1):
# #存在部分报错，所以用try来跳过报错。
#     try: 
#       model = ARIMA(D_data, order=(p,0,q)).fit()  #这里d选0是因为D_data已经差分过了，如果用原数据x则将d取1
#       tmp.append(model.bic)
#       print(f"p={p}, q={q} BIC计算成功: {model.bic}")
#     except:
#       tmp.append(None)
#       print("错误")
#   bic_matrix.append(tmp)
#从中可以找出最小值
bic_matrix = pd.DataFrame(bic_matrix) 
#先用stack展平，然后用idxmin找出最小值位置。
#p,q = bic_matrix.stack().idxmin() 
#print(u'BIC最小的p值和q值为：%s、%s' %(p,q))
# 取BIC信息量达到最小的模型阶数，结果p为0，q为1，定阶完成。

# 7、建立模型和预测
model = ARIMA(D_data, order = (0,0,0)).fit() 
#给出一份模型报告
# model.summary2() 
#作为期5天的预测，返回预测结果、标准误差、置信区间。
print(model.forecast(5))